package com.atguigu.boot_redis04.controller;

import com.atguigu.boot_redis04.vo.UserRegistVo;
import com.atguigu.common.constant.constant.AuthServerConstant;
import com.atguigu.common.exception.BizCodeEnume;
import com.atguigu.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 *
 * @author gcq
 * @Create 2021-01-11
 */
@Slf4j
@Controller
public class AuthController {

    @Autowired
    StringRedisTemplate redisTemplate;

    /**
     * 发送验证码
     *
     * @param phone
     * @return
     */
    @GetMapping("/sms/sendCode")
    @ResponseBody
    public R sendCode(@RequestParam("phone") String phone) {
        /**
         * 1、接口防刷，防止同一个phone在60秒内重复再次发送验证码
         * 2、验证码的再次校验，存redis，因为这个验证码不是永远存储的
         * 2.1、存入redis，key是一定包含手机号的，值是验证码
         * 2.3、如果是60秒以后，那么在发送新的验证码
         *
         * 验证码发送频率过多的解决方案：
         * 1、保存验证码的时候，再保存当前验证码设置的系统时间。
         *      1.1、只要你想要发送验证码，先去看下redis中有没有
         *      1.2、如果有了，再去看下这个时间，如果这个时间再60秒内，那就60秒以后再发送
         *
         *  key:sms:code:+Phone
         *  value: code(随机码)_当前时间毫秒数
         */
        // 1、先从redis中查询是否存在验证码
        String redisCode = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
        // 2、如果验证码不为空，则验证该验证码是否60秒内
        if (!StringUtils.isEmpty(redisCode)) {
            // 3、验证短信发送时间是否在60秒内
            long saveCodeTime = Long.parseLong(redisCode.split("_")[1]); // value = code_当前时间加毫秒数
            // 3.1 如果当前时间减去验证码存入redis的时间小于60秒 ，说明用户在一分钟内多次获取验证码
            if (System.currentTimeMillis() - saveCodeTime < 60000) {
                // 返回验证码获取频率过高
                return R.error(BizCodeEnume.SMS_CODE_EXCEPTION.getCode(), BizCodeEnume.SMS_CODE_EXCEPTION.getMsg());
            }
        }
        // 4、生成验证码 使用UUID生成会有英文，对用户不友好
        int randomCode = (int) (Math.random() * 100000); // 生成五位随机数字
        String code = String.valueOf(randomCode);
        // 5、拼装值 验证码加上当前时间毫秒
        String substring = code + "_" + System.currentTimeMillis();
        // 6、存入redis k->指定前缀加上手机号 value-> 拼装值  time-> 10分钟过期时间，防止用户发送验证码
        redisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone, substring, 10, TimeUnit.MINUTES);
        // 7、调用第三方服务根据手机发送验证码
//        thirdFeignService.sendCode(phone,code);
        return R.ok();
    }

    /**
     * @需求描述: 系统管理员-认证服务-用户注册
     * @创建人: 郭承乾
     * @创建时间: 2020/12/31 10:53
     * @修改需求:
     * @修改人:
     * @修改时间:
     * @需求思路: RedirectAttributes 模拟重定向携带数据
     * 原理：
     * 利用session原理，将数据放到session中，然后重定向到页面中再取出来，
     * 跳到下一个页面，然后取出数据以后，session里面的数据就会删掉
     */
    @PostMapping("/regist")
    public String regist(@Valid UserRegistVo registVo, BindingResult result, RedirectAttributes redirectAttributes) {
        // 1、如果校验出错，那么说明参数有问题
        if (result.hasErrors()) {
            // 1.1、将错误结果转成map key为受影响的字段 value为错误结果
            Map<String, String> errorMap = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
            // 1.2、将错误放到 redirect中,存放错误信息，让前端感知并获取错误信息
            redirectAttributes.addFlashAttribute("errors", errorMap);
            // 1.3、重定向到注册页面并显示出注册错误信息
            return "redirect:http://localhost:8080/reg.html"; // 注册页
        }
        
        String code = registVo.getCode();
        // 2、用户提交的验证码和redis存的验证码进行比较，如果一致，那说明该验证码正确，如果不一致，说明验证码不正确
        String redisCode = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + registVo.getPhone());
        if (!StringUtils.isEmpty(redisCode)) { // 前提是验证码不能为空
            // 3、redis里面存了这个手机号对应的验证码,验证正确，就对结果进行截串
            if (code.equals(redisCode.split("_")[0])) {
                // 把redis里面存的验证码进行删除，如果下次还带着之前的验证码过来，就会验证失败
                redisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + registVo.getPhone());
                // 验证码通过，如果上面校验并无出错，那么调用远程服务gulimall-member进行注册
                // 这里为了演示，只要验证码匹配就返回用户注册成功
                    Map<String, String> errors = new HashMap<>();
                    errors.put("msg","登录成功");
                    // 存放错误信息，让前端感知并获取错误信息
                    redirectAttributes.addFlashAttribute("errors", errors);
                    return "redirect:http://localhost:8080/login.html"; // 登录页面
            } else {
                // 验证码不通过
                Map<String, String> errors = new HashMap<>();
                errors.put("code", "验证码不匹配");
                // 存放错误信息，让前端感知并获取错误信息
                redirectAttributes.addFlashAttribute("errors", errors);
                return "redirect:http://localhost:8080/reg.html"; // 注册页
            }
        } else { // 验证码为空
            // redis存验证码过期
            Map<String, String> errors = new HashMap<>();
            errors.put("code", "验证码为空");
            // 存放错误信息，让前端感知并获取错误信息
            redirectAttributes.addFlashAttribute("errors", errors);
            return "redirect:http://localhost:8080/reg.html"; // 注册页
        }
    }
}